# -*- coding: utf-8 -*-
#	Copyright 2005,2006,2007,2008 Spike^ekipS <spikeekips@gmail.com>
#
#	   This program is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	This program is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this program; if not, write to the Free Software
#	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

try :
	import lucene
	env = lucene.initVM(lucene.CLASSPATH)
except :
	raise ImportError, "Install pyLucene."

import os, datetime
from django.utils.html import escape
from django.db.models.loading import get_model

import pylucene

HIGHLIGHT_TAG = ("""<span class="highlight">""", "</span>", )

FIELD_NAME_UID		= "__uid__"
FIELD_NAME_PK		= "__pk__"
FIELD_NAME_MODEL	= "__model__"

class DocumentFieldObject (object) :
	def __init__ (self, do, field) :
		self.document_object = do
		self.field = field
		self.verbose_name = field.replace("_", " ")
		self.name = field

	def get_terms (self) :
		reader = pylucene.Reader()
		dns = reader.reader.termDocs(lucene.Term(
			FIELD_NAME_UID, self.document_object.get(FIELD_NAME_UID))
		)
		dns.next()
		dn = dns.doc()

		try :
			ts = reader.reader.getTermFreqVector(dn, self.field).getTerms()
		except AttributeError :
			return None

		reader.close()

		return ts

class DocumentObjectMeta (object) :
	verbose_name = None
	app_label = None

	def __init__ (self, doc) :
		_m = doc.get(FIELD_NAME_MODEL).split(".")
		self.verbose_name = _m[1]
		self.app_label = _m[0]

		class PK (object) :
			name = "id"

		self.pk = PK()

class DocumentObject (dict) :
	fields_object = list()

	def __init__ (self, hit, model=None, queryset=None, query=None, ) :
		self.hit = hit[0]
		self.doc = hit[1]

		if model is None :
			m = self.doc.get("__model__").split(".")
			model = get_model(m[0], m[1])

		self._meta = DocumentObjectMeta(self.doc)

		self.queryset = queryset
		self.query = query
		self.merge()

		self.highlighter = None
		self.highlighted = dict()

	def get_meta (self) :
		return self._meta

	def get_model_name (self) :
		return self.get(FIELD_NAME_MODEL)

	def get_field (self, field_name) :
		try :
			return DocumentFieldObject(self.doc, field_name)
		except :
			return None

	def getScore (self) :
		return self.hit.getScore()

	def merge (self) :
		_m = MODELS_REGISTERED.get(self.doc.get("__model__"), None)
		if _m is None :
			for i in self.doc.fields() :
				self[lucene.Field.cast_(i).name()] = self.doc.get(lucene.Field.cast_(i).name())

			return

		_r = list()
		for k, i in _m.get("fields").items() :
			f = self.doc.getFields(k)
			if not f : continue

			if len(f) < 2 :
				f = f[0]
				v = f.stringValue()
			else :
				v = [j.stringValue() for j in f]

			_r.append((k, get_value_index_to_object(i.get("type"), v, queryset=self.queryset), ))

		# merge default.
		for i in (FIELD_NAME_UID, FIELD_NAME_MODEL) :
			_r.append(
				(i, get_value_index_to_object("keyword", self.doc.getField(i).stringValue()), )
			)

		_r.append(
			(FIELD_NAME_PK, get_value_index_to_object("int", self.doc.getField(FIELD_NAME_PK).stringValue()), )
		)

		self.update(_r)

	def items_view (self) :
		_m = MODELS_REGISTERED.get(self.doc.get("__model__"), None)
		for k, i in _m.get("fields").items() :
			yield (DocumentFieldObject(self.doc, k), self.get(k), )

	def __getitem__ (self, k) :
		if k in ("pk", ) :
			k = FIELD_NAME_PK

		if self.has_key(k) :
			return super(DocumentObject, self).__getitem__(k)
		elif self.doc.get(k, None) :
			return self.doc.get(k, None)

		raise KeyError, k

	__getattr__ = __getitem__

	def __unicode__ (self) :
		return "<DocumentIndexed: %s>" % self.get(FIELD_NAME_PK)

	highlighted = dict()
	def get_highlighted (self, field_name, queryset=None, query=None) :
		if not self.highlighted.get(field_name, None) :
			s = self.doc.get(field_name)
			if type(s) in (list, tuple) :
				s = " ".join(s)

			if not s :
				self.highlighted[field_name] = [s, ]
				return self.highlighted[field_name]

			if query is None :
				query = self.query
			elif queryset is None :
				queryset = self.queryset
				if queryset :
					(query, ordering, ) = self.queryset.query.as_sql()

			if query is None :
				self.highlighted[field_name] = [s, ]
				return self.highlighted[field_name]

			if self.highlighter is None :
				self.highlighter = lucene.Highlighter(
					lucene.SimpleHTMLFormatter(*HIGHLIGHT_TAG),
					lucene.QueryScorer(query),
				)
				self.highlighter.setTextFragmenter(lucene.SimpleFragmenter(1000))

			tokenStream = lucene.CJKAnalyzer().tokenStream(
				field_name,
				lucene.StringReader(escape(s)),
			)

			tf = self.highlighter.getBestTextFragments(
				tokenStream,
				escape(self.doc.get(field_name)),
				True,
				10,
			)
			tokenStream.close()

			self.highlighted[field_name] = list()
			for i in tf :
				if not i : continue
				if i.getScore() < 0 : continue

				self.highlighted[field_name].append(i.toString())

		return self.highlighted[field_name]

	def highlight (self, field_name, queryset=None, query=None) :
		if not self.get(field_name) : return self.get(field_name)

		r = self.get_highlighted(field_name, queryset=queryset, query=query)
		if len(r) < 1 :
			return self.get(field_name)

		return "".join(r)

def get_fields_from_model (model) :
	try :
		__fields = model._meta.fields_index
	except AttributeError :
		__fields = list()

	if len(__fields) < 1 :
		__fields = list()
		for f in model._meta.fields :
			__fields.append(f.name)

	return [model._meta.get_field(i) for i in list(set(__fields))]

def translate_datetime_to_str (dt) :
	return dt.strftime("%Y%m%d%H%M%S")

def translate_str_to_datetime (dt, kind=None) :
	d = datetime.datetime.strptime(dt, "%Y%m%d%H%M%S")
	if kind :
		_y, _m, _d = d.year, d.month, d.day
		if kind == "year" :
			_m, _d, = 1, 1
		elif kind == "month" :
			_d = 1

		return datetime.date(_y, _m, _d)
	else :
		return d

def get_value_object_to_index (f_type, value) :
	if type(value) in (list, tuple, ) :
		return [get_value_object_to_index(f_type, i) for i in value]

	if f_type.__class__ in pylucene.FIELDS_STR :
		return value
	elif f_type.__class__ in pylucene.FIELDS_INT :
		return str(value)
	elif f_type.__class__ in pylucene.FIELDS_DATE :
		if isinstance(value, datetime.datetime) :
			return translate_datetime_to_str(value)
		else :
			return str(value)
	elif f_type.__class__ in pylucene.FIELDS_META :
		return value
	elif f_type.__class__ in pylucene.FIELDS_MULTI_KEYWORD :
		if type(value) in (str, unicode, ) :
			values = list()
			subpaths = [i for i in value.split(os.sep)]
			for i in xrange(len(subpaths)) :
				values.append("/".join(subpaths[:i+1]))
			return values
		else :
			return value
	else :
		return value

def get_value_index_to_object (f_type, value, queryset=None) :
	if f_type in ("str", "keyword", "multi-keyword", ) :
		return value
	elif f_type == "int" :
		return int(value)
	elif f_type == "date" :
		if hasattr(queryset, "_kind") and queryset._kind :
			return translate_str_to_datetime(value, queryset._kind)
		else :
			return translate_str_to_datetime(value)
	elif f_type == "meta" :
		return value
	else :
		return value

def flatten_indexed_value (f_type, value) :
	if f_type.__class__ in pylucene.FIELDS_STR :
		return get_value_object_to_index(f_type, value)
	elif f_type.__class__ in pylucene.FIELDS_INT :
		return get_value_object_to_index(f_type, value)
	elif f_type.__class__ in pylucene.FIELDS_DATE :
		return get_value_object_to_index(f_type, value)
	elif f_type.__class__ in pylucene.FIELDS_META :
		return value
	elif f_type.__class__ in pylucene.FIELDS_MULTI_KEYWORD :
		return value
	else :
		return value

def get_model_name (model) :
	return "%s.%s" % (model._meta.app_label, model._meta.object_name)

def get_uid (model, pk) :
	return "%s/%s" % (get_model_name(model), pk, )

def get_object_uid (obj) :
	return get_uid(obj, obj.pk)

def create_field (field, value, store=True, tokenize=True, add_sort=False, analyzer=None, ) :
	__terms = list()
	if not value :
		__terms.append((value, True, False,))
	else :
		v = get_value_object_to_index(field, value)
		if type(v) in (str, unicode, ) :
			__terms.append((v, store, tokenize,))
		elif type(v) in (list, tuple, ) :
			for i in v :
				if i.strip() :
					__terms.append((i, True, False,))

	terms = list()
	for i in __terms :
		terms.append(
			lucene.Field(
				field.name,
				i[0],
				i[1] and lucene.Field.Store.YES or lucene.Field.Store.NO,
				i[2] and lucene.Field.Index.TOKENIZED or lucene.Field.Index.UN_TOKENIZED,
				lucene.Field.TermVector.WITH_POSITIONS_OFFSETS,
			)
		)

	# add sorting field
	if __terms and add_sort :
		terms.append(
			lucene.Field(
				"sort__%s" % field.name,
				flatten_indexed_value(field, value),
				lucene.Field.Store.YES,
				lucene.Field.Index.UN_TOKENIZED,
			)
		)

	return terms

######################################################################
# signals
def func_pre_save (instance=None, sender=None, created=False, **kwargs) :
	pass

def func_post_save (instance=None, sender=None, created=False, **kwargs) :
	pylucene.IndexWriter().index(instance).close()

def func_pre_delete (instance=None, sender=None, **kwargs) :
	pass

def func_post_delete (instance=None, sender=None, **kwargs) :
	pylucene.IndexWriter().unindex(instance).close()

def analyze_model (model) :
	fields = dict()
	for f in get_fields_from_model(model) :
		a = analyze_model_field(f)
		if a is None :
			continue

		fields[f.name] = a

	return fields

def analyze_model_field (f) :
	analyzer = lucene.CJKAnalyzer()
	add_sort = True

	_type = get_field_type(f)
	if _type is None :
		return None

	attr = pylucene.FIELDS_TYPE.get(_type, None)
	if attr is None :
		attr = {
			"store": True,
			"tokenize": True,
		}

	attr.update(
		{
			"field": f,
			"type": _type,
			"add_sort": add_sort,
			"analyzer": analyzer,
			"name": f.name,
		}
	)
	return attr

def get_field_type (f) :
	if f.__class__ in pylucene.FIELDS_STR :
		return "keyword"
	elif f.__class__ in pylucene.FIELDS_INT :
		return "int"
	elif f.__class__ in pylucene.FIELDS_DATE :
		return "date"
	elif f.__class__ in pylucene.FIELDS_META :
		return "meta"
	elif f.__class__ in pylucene.FIELDS_MULTI_KEYWORD :
		return "multi-keyword"
	else :
		return None

MODELS_REGISTERED = dict()
def register_model (model) :
	from models_manager import SearchManager

	model.objects_search = SearchManager()
	model.objects_search.contribute_to_class(model, "objects_search")

	# analyze fields

	MODELS_REGISTERED.update(
		{
			get_model_name(model): {
				"name": get_model_name(model),
				"model": model,
				"fields": analyze_model(model),
			},
		}
	)

register = register_model

def get_registered_model () :
	return MODELS_REGISTERED

def search (query, order_by=None, ) :
	if type(query) in (str, unicode, int, long, float, ) :
		query = pylucene.parse_query(query)
	elif isinstance(query, lucene.Query) :
		pass
	else :
		raise RuntimeError, "Invalid query."

	try :
		s = pylucene.Searcher(query)
		r = s.search(query)
	except :
		return (list(), query, )

	return (r, query, )



"""
Description
-----------


ChangeLog
---------


Usage
-----


"""

__author__ =  "Spike^ekipS <spikeekips@gmail.com>"




